package retrofit.boot.apache.client;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import retrofit.boot.client.IRetrofitClient;
import retrofit.boot.client.RequestWrapper;
import retrofit.client.Header;
import retrofit.client.Request;
import retrofit.client.Response;
import retrofit.mime.TypedByteArray;
import retrofit.mime.TypedOutput;

import java.io.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPOutputStream;

/**
 * Created by wulg on 2019/10/22
 * description:
 */
public class ApacheHttpClient implements IRetrofitClient<RequestWrapper, Response> {

    private final HttpClient client;

    private final RequestConfig defalutRequestConfig;

    public ApacheHttpClient(HttpClient client, RequestConfig defalutRequestConfig) {
        this.client = client;
        this.defalutRequestConfig = defalutRequestConfig;
    }

    @Override
    public Response execute(RequestWrapper wrapper) throws IOException {
        Request request = wrapper.getRequest();
        RequestWrapper.Options options = wrapper.getOptions();
        HttpUriRequest httpRequest = createRequest(request);
        HttpClientContext httpContext = HttpClientContext.create();
        if(defalutRequestConfig != null && options.getTimeout() != null){//定制requestConfig
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectionRequestTimeout(defalutRequestConfig.getConnectionRequestTimeout())
                    .setConnectTimeout(defalutRequestConfig.getConnectTimeout())
                    .setSocketTimeout(options.getTimeout().intValue()).build();
            httpContext.setRequestConfig(requestConfig);
        }
        HttpResponse httpResponse = client.execute(httpRequest,httpContext);
        return parseResponse(request.getUrl(), httpResponse);
    }

    static HttpUriRequest createRequest(Request request) {
        if (request.getBody() != null) {
            return new GenericEntityHttpRequest(request);
        }
        return new GenericHttpRequest(request);
    }

    static Response parseResponse(String url, HttpResponse response) throws IOException {
        StatusLine statusLine = response.getStatusLine();
        int status = statusLine.getStatusCode();
        String reason = statusLine.getReasonPhrase();
        List<Header> headers = new ArrayList<>();
        String contentType = "application/octet-stream";
        for (org.apache.http.Header header : response.getAllHeaders()) {
            String name = header.getName();
            String value = header.getValue();
            if ("Content-Type".equalsIgnoreCase(name)) {
                contentType = value;
            }
            headers.add(new Header(name, value));
        }
        TypedByteArray body = null;
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            byte[] bytes = EntityUtils.toByteArray(entity);
            body = new TypedByteArray(contentType, bytes);
            EntityUtils.consume(entity);
        }
        return new Response(url, status, reason, headers, body);
    }

    private static class GenericHttpRequest extends HttpRequestBase {
        private final String method;

        public GenericHttpRequest(Request request) {
            method = request.getMethod();
            setURI(URI.create(request.getUrl()));
            for (Header header : request.getHeaders()) {
                addHeader(new BasicHeader(header.getName(), header.getValue()));
            }
        }

        @Override public String getMethod() {
            return method;
        }
    }

    private static class GenericEntityHttpRequest extends HttpEntityEnclosingRequestBase {
        private final String method;

        GenericEntityHttpRequest(Request request) {
            super();
            method = request.getMethod();
            setURI(URI.create(request.getUrl()));
            for (Header header : request.getHeaders()) {
                addHeader(new BasicHeader(header.getName(), header.getValue()));
            }
            setEntity(new TypedOutputEntity(request.getBody(),this.gzipIfNecessary(request.getHeaders())));
        }

        @Override public String getMethod() {
            return method;
        }

        private boolean gzipIfNecessary(List<Header> headers){
            if(CollectionUtils.isNotEmpty(headers)){
                for(Header header : headers){
                    if(StringUtils.equalsIgnoreCase("Content-Encoding",header.getName()) ||
                            StringUtils.equalsIgnoreCase("gzip",header.getValue())){
                        return true;
                    }
                }
            }
            return false;
        }
    }

    static class TypedOutputEntity extends AbstractHttpEntity {
        final TypedOutput typedOutput;

        final boolean gzipIfNecessary;

        TypedOutputEntity(TypedOutput typedOutput,boolean gzipIfNecessary) {
            this.typedOutput = typedOutput;
            this.gzipIfNecessary = gzipIfNecessary;
            setContentType(typedOutput.mimeType());
        }

        @Override
        public boolean isRepeatable() {
            return true;
        }

        @Override
        public long getContentLength() {
            return typedOutput.length();
        }

        @Override
        public InputStream getContent() throws IOException {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            typedOutput.writeTo(out);
            return new ByteArrayInputStream(out.toByteArray());
        }

        @Override
        public void writeTo(OutputStream out) throws IOException {
            if(!gzipIfNecessary){
                typedOutput.writeTo(out);
            } else{
                ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
                GZIPOutputStream gzip = null;
                try {
                    gzip = new GZIPOutputStream(byteArrayOut);
                    typedOutput.writeTo(gzip);
                    out.write(byteArrayOut.toByteArray());
                }  finally {
                    if (byteArrayOut != null) {
                        byteArrayOut.close();
                    }
                    if (gzip != null) {
                        gzip.close();
                    }
                }
            }
        }

        @Override
        public boolean isStreaming() {
            return false;
        }
    }
}
